app.cfg = {
    host: '175.10.33.182:9317',
    headers: {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    },
    speaking: false,
    /*窗口坐标*/
    btnTitle: '启动监听股票MACD指标', windowX: 160, windowY: 0, rawY: 0, downTime: 0,
    /*执行状态：0停止、1启动*/
    state: 0,
    version: 'V2025.11.06',
    history: [
        '1.【新增】自动下单',
        '2.【新增】在线更新脚本',
        '3.【新增】程序退出',
    ],
    downURL: 'https://gitee.com/monvxiaoxi/autojs/raw/master/ui/同花顺自动交易助手.js'
}
app.ths = {
    editsUI: void 0,
    vol: 10, // 买卖时的数量为10张
    buy_click: {x: 0, y:0},  // 买时价格点击坐标
    sale_click: {x: 0, y:0}, // 卖时价格点击坐标
    tradeTime: 0 // 交易时间
},
/*调用系统语音合成*/
app.speak = function initTTS(content) {
    if (!app.TTS) {
        importPackage(android.speech.tts)
        importClass(java.util.Locale)
        app.TTS = new TextToSpeech(context, function (status) {
            app.TTS.setLanguage(Locale.CHINA)
        })
        sleep(1000)
    }
    if (!app.cfg.speaking) {
        app.cfg.speaking = true
        app.TTS.speak(content, TextToSpeech.QUEUE_ADD, null)
        setTimeout(() => {app.cfg.speaking = false}, 2000)
    }
    
}

/*程序入口*/
!function (){
    launch(getPackageName('同花顺')) || launchApp('同花顺')
    initWindow()
    setInterval(_=>{
        if (app.cfg.state == 1) {
            threads.start(function(){heart_beat()})
            ui.run(function(){window.ok.setText(`${app.cfg.title||app.cfg.btnTitle}`)})
        }
    }, 1000)
}()

/*同花顺k线接口*/
function getKlineDataList(code, klt, lmt){
    try {
        quoteId = code.startsWith("12") || code.startsWith("00") || code.startsWith("30") ? `0.${code}` : `1.${code}`
        params = {
            'secid': quoteId,
            'ut': 'fa5fd1943c7b386f172d6893dbfba10b',
            'fields1': 'f1,f2,f3,f4,f5,f6',
            'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60',
            'klt': klt,
            'fqt': 1,
            'end': '20500101',
            'lmt': lmt || 100,
            '_': new Date().getTime(),
        }
        const {headers} = app.cfg
        const qs = Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&')
        const website = 'https://push2his.eastmoney.com/api/qt/stock/kline/get?'
        const rt = http.get(website+qs, {headers}).body.json().data
        const klines = []
        if (rt.klines) {
            rt.klines.forEach(kline => {
                klines.push(kline.split(','))
            })
            return {name:rt.name, code: rt.code, klines}
        }else{
            dostop('同花顺接口可能被封禁，请稍后再试！')
        }
    } catch (err) {
        log('同花顺接口异常:', err)
        dostop('请求同花顺接口发生异常，可能ip被封禁！')
    }
}

function heart_beat(){
    const {code, host, skToken} = app.cfg
    const fs1 = getKlineDataList(code, 1)
    if (fs1) {
        const rt = http.post(`http://${host}/stock/${skToken}/heart_beat`,{
            fs1_klines: JSON.stringify([fs1.klines.pop()])
        }).body.json()
        const arr = rt.data
        app.cfg.title = `(${arr[0]}) macd:${arr[1]},  dif:${arr[2]},  dea:${arr[3]} ${arr[4]}`
        if (arr[5] > 0 && new Date().getTime()-app.ths.tradeTime > 5*60*1000) {
            // 记录交易时间，5分钟内禁止二次下单
            app.ths.tradeTime = new Date().getTime() 
            // arr -> 4信号描述， 5信号强弱值越大代表买卖点越强烈，6买或卖
            app.speak(`请注意已发现${arr[4]}信号`)
            // arr[6] -> 0买，1卖
            if (arr[6]==0) { buy() } else { sale() }
        }
    }
}

function buy(){
    const {buy_click, editsUI, vol} = app.ths 
    // 点击取卖一价格买入
    click(buy_click.x, buy_click.y)
    textStartsWith('买 入').click() 
    sleep(500)
    click('确认买入') && sleep(500)
    click('确定')
    sleep(1000)
    editsUI[2].setText(vol)
}

function sale() {
    const {sale_click, editsUI, vol} = app.ths 
    // 点击取买一价格卖出
    click(sale_click.x, sale_click.y)
    textStartsWith('卖 出').click() 
    sleep(500)
    click('确认卖出') && sleep(500)
    click('确定')
    sleep(1000)
    editsUI[2].setText(vol)
}

/*启动*/
function dostart(){
    if (!className('HorizontalScrollView').boundsInside(0, 100, device.width, 400).exists()) {
        alert('请前往下单界面再点击启动!')
        return
    }
    const editsUI = className('EditText').find()
    const code = editsUI[0].text()
    if (!/\d{6}/.test(code)) {
        alert('请输入股票代码!')
        return
    }
    editsUI[2].setText(app.ths.vol)
    app.ths.editsUI = editsUI
    bds = id('five_buy_sale').findOne().bounds()
    app.ths.buy_click = {x: bds.centerX(), y:bds.centerY()-20}
    app.ths.sale_click = {x: bds.centerX(), y:bds.centerY()+20}
    if (app.cfg.state == 0) {
        threads.start(function(){
            const fs1 = getKlineDataList(code, 1)
            if (fs1) {
                const fs5 = getKlineDataList(code, 5, 100)
                const deviceId = device.getAndroidId()
                const host = app.cfg.host
                const rt = http.post(`http://${host}/stock/${deviceId}/push`,{
                        name: fs1.name, code: fs1.code, 
                        fs1_klines: JSON.stringify(fs1.klines), 
                        fs5_klines: JSON.stringify(fs5.klines)
                }).body.json()
                app.cfg.skToken = rt.sk_token
                app.cfg.code = code
                app.cfg.title = '正在执行，请勿动屏幕...'
                app.cfg.state = 1
            }
        })
    }
}

/*停止*/
function dostop(text){
    setTimeout(() => {
        if (app.cfg.state == 1) {
            try {threads.shutDownAll()} catch (err) {log('停止脚本运行异常', err)}
            app.cfg.state = 0
            ui.run(function(){window.ok.setText(`${app.cfg.btnTitle}`)})
        } 
        text && alert(text)
    }, 500)
}

/*初始化操作窗口*/
function initWindow(){
    window = floaty.rawWindow(
        <frame w="{{device.width-(app.cfg.windowX*2)}}px" bg="#289AD6" h="36">
            <horizontal>
                <button id="ok" text="{{app.cfg.btnTitle}}" w="*" alpha="0.8" color="#ffffff" style="Widget.AppCompat.Button.Borderless"/>
            </horizontal>
        </frame>
    )
    window.setPosition(app.cfg.windowX, app.cfg.windowY)
    window.ok.setOnTouchListener(function (view, event) {
        if (event.getAction() == event.ACTION_DOWN) {
            app.cfg.rawY = event.getRawY()
            app.cfg.windowY = window.getY()
            app.cfg.downTime = new Date().getTime()
        }
        else if (event.getAction() == event.ACTION_MOVE){
            let my = event.getRawY() - app.cfg.rawY
            if (my > 10 || my < 0) {
                let py = app.cfg.windowY + (event.getRawY() - app.cfg.rawY)
                let maxHeight = device.height - device.getVirtualBarHeigh() - 100
                window.setPosition(app.cfg.windowX, py > maxHeight ? maxHeight : py < 0 ? 0 : py)
            }
        }
        else if (event.getAction() == event.ACTION_UP) {
            if (Math.abs(event.getRawY() - app.cfg.rawY) < 10 && (new Date().getTime() - app.cfg.downTime) <= 250) {
                /*点击事件*/
                (app.cfg.state == 0 ? dostart() : dostop())
            } else if (app.cfg.state == 0 && Math.abs(event.getRawY() - app.cfg.rawY) < 10 && (new Date().getTime() - app.cfg.downTime) > 250) {
                /*长按事件*/
                dialogs.select('功能&设置', ['触发参数', '检查更新(Check)', '退出(Exit)']).then(i => {
                    i == 1 && onlineCheckUpdate()
                    i == 2 && exit()
                })
            }
        }
        return true
    })
    return true
}

/*在线更新源码*/
function onlineCheckUpdate(){
    const {version, history, downURL} = app.cfg
    dialogs.build({
        title: `检查更新脚本-${version}`,
        content: history.join('\n'),
        positive: "更新",
        negative: "取消"
    })
    .on("positive", () => {
        app.downloadDialog = dialogs.build({
            title: "下载中...",
            negative: "取消",
            progress: {max: 100, showMinMax: true},
            autoDismiss: false
        }).on("negative", ()=>{app.downloadDialog.dismiss() || (app.downloadDialog = null)}).show()
        app.downloadDialog.setProgress(90)
        threads.start(function(){
            const {statusCode, body} =  http.get(downURL)
            if (statusCode == 200) {
                const fileName = downURL.substring(downURL.lastIndexOf('/')+1)
                const filePath = `${files.cwd()}/${fileName}`
                files.writeBytes(filePath, body.bytes())
                app.downloadDialog.setProgress(100) || sleep(1000)
                app.downloadDialog.dismiss() || (app.downloadDialog = null)
                alert('下载成功，程序将退出。\n请重新启动程序！')
                exit()
            }
        })
    }).show()
}